/* Puyan Lotfi
 * Berkeley Sockets Abstaction
 */

#ifdef _WIN32
#include <winsock.h>
#else
#include <unistd.h>             /* close()          */
#include <sys/socket.h>         /* Socket Functions */
#include <sys/types.h>          /* Socket Datatypes */
#include <netinet/in.h>         /* IP Datatypes     */
#include <arpa/inet.h>          /* Address Structs  */
#endif

#include <stdio.h>              /* Standard IO      */
#include <stdlib.h>             /* Standard Lib     */
#include <string.h>             /* String Libs      */
#include <pthread.h>            /* Pthreads         */
#include "include/sock_lib.h"   /* My Functions     */
#include "include/defs.h"       /* Definitions      */
#include "include/http.h"
#include "include/sync.h"
#include "include/thread_pool.h"
#define MAXPENDING 5            /* Max Requests     */
#define REUSE_PORT 1


/*
 * Producer function
 */
void *producer(void* v_port_number) {
    int        clntSock;                          /* client sock descriptor.      */
    int        servSock;                          /* Server sock descriptor.      */

    unsigned short port_number = *((unsigned short*)v_port_number);
    
    #ifdef _WIN32
        DWORD  threadID;                              /* ThreadID from CreateThread().*/
        WSADATA wsaData;                              /* Winsock struct.              */
        if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)/* Winsock 2.0 DLL.             */
            DieWithError("WSAStartup() failed");
    #endif

    VPRINTF((" Producer Port %d\n", port_number));

    servSock = ContructTCPSocket(port_number);

    #if _LOG_
    printf("[PRODUCER] Producer Initialized, listening for connections\n");
    fflush(stdout);
    #endif

    for(;;)                                       /* Loop forever                 */
    {
        clntSock = AcceptConnection(servSock);
        if (clntSock == -1) {
            continue;
        }
        addSocket(clntSock);
    }
                                                    /* NEVER REACHED...             */
}

int ContructTCPSocket(unsigned short port_number)
{
  int        servSock;                          /* Server sock descriptor.      */
  int        opt;                               /* Reuse ocupied port           */
  struct     sockaddr_in  ServAddr;             /* Server Socket addr           */ 
  struct     sockaddr_in  ClntAddr;             /* Client Socket addr           */ 
 
  opt      = REUSE_PORT;                        /* reuse port sockopt           */
                                                /* Construct local              */
                                                /* address structure            */
  memset( &ServAddr, 0, sizeof(ServAddr) );     /* Zero serve  struct           */
  memset( &ClntAddr, 0, sizeof(ClntAddr) );     /* Zero client struct           */
  ServAddr.sin_family = AF_INET;                /* Inet addr family             */
  ServAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming iface           */
  ServAddr.sin_port = htons(port_number);       /* Local (server) port          */

  if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 
    DieWithError("socket() failed");            /* create socket                */

  if (setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) 
    DieWithError("setsockopt() failed");        /* sockopt: reuse ports         */

  if (bind(servSock, (struct sockaddr *) &ServAddr, sizeof(ServAddr)) < 0) 
    DieWithError("bind() failed");              /* bind to the port             */

  if (listen(servSock, MAXPENDING) < 0) 
    DieWithError("listen() failed");

  return servSock; 
}

int AcceptConnection(int server_socket)
{
  int        clntSock;                          /* client sock descriptor.      */
  struct     sockaddr_in  ClntAddr;             /* Client Socket addr           */ 
  unsigned   int          ClntLen;              /* client address               * 
                                                 * struct length                */
  ClntLen  = sizeof(ClntAddr);                  /* Client addr length           */

  VPRINTF(("[PRODUCER] Waiting to accept a connection\n"));
  

  if ((clntSock = accept(server_socket, (struct sockaddr *) &ClntAddr, &ClntLen)) < 0) {
      //DieWithError("accept() failed");          /* Wait for a client to connect */
      clntSock = -1; //negative socket, so it knows to ignore this connection
  }

  VPRINTF(("Handling client %s\n", inet_ntoa(ClntAddr.sin_addr)));

  return clntSock;
}

/*
 * Consumer Function
 */
void *consumer(void* dumb)
{
    char      *curToSend;
    int       clntSocket;  
    char      curToRecv[BUFFERSIZE];
    char      *curToRecv_ptr;
    char      *dgramPtr;
    int       totalBytesRecv;   
    int       newBytesRecv;
    int       i;

    #if _LOG_
    #ifndef _WIN32
        printf("[CONSUMER] Consumer %ld started\n", (long)pthread_self());
        fflush(stdout);
    #endif
    #endif

    for (;;) {
        
        clntSocket = removeSocket(); //This is where it waits!!!  

        #if _LOG_
        printf("[CONSUMER] Handling Socket %d\n",clntSocket);
        fflush(stdout); 
        #endif
            
        memset(curToRecv, 0, sizeof(char)*BUFFERSIZE);
            
        for(i=0,totalBytesRecv=0, newBytesRecv=0, dgramPtr=curToRecv; 
            totalBytesRecv < sizeof(curToRecv); totalBytesRecv+=newBytesRecv,i++)
        {
            
                VPRINTF(("Receiving %d Bytes. %d Bytes Total.\n", 
	                 newBytesRecv, totalBytesRecv));
            

            if(((newBytesRecv = recv(clntSocket, dgramPtr, 
                sizeof(curToRecv) - totalBytesRecv, 0)) <= 0))
            {
                VPRINTF(("recv() failed\n"));
                VPRINTF(("Kill connection line %d.\n", __LINE__));
                
                #ifdef _WIN32
	                closesocket(clntSocket);
                #else
	                close(clntSocket); 
                #endif
                //pthread_exit(NULL);
            } else {
                dgramPtr += newBytesRecv;

                VPRINTF(("%s\n", curToRecv));

                if(NULL != (curToSend = strtok_r(curToRecv, "\n", &curToRecv_ptr)))
                {
                    if(curToRecv[strlen(curToSend)-1] == '\r')
                    {
	                    VPRINTF(("GOT A CR\n"));
	                    
	                    break; /* HACK */
	                    if(curToRecv[strlen(curToSend)-3] == '\r'&&
	                       curToRecv[strlen(curToSend)-2] == '\n')
	                    {
	                        break;
	                    } else {
	                        curToRecv[strlen(curToSend)] = '\n';
	                        continue;
                        }
                    } else {
	                    curToRecv[strlen(curToSend)] = '\n';
	                    continue;
                    }
                }
            }
        } //end inner for()
        

        VPRINTF(("Out of the Recv() Loop\n"));
        VPRINTF(("clntSocket: %d\n",clntSocket));
        fflush(stdout); 
        
	if (!curToSend) {
            VPRINTF(("curToSend == NULL :(\n"));
            fflush(stdout);  
        } else {
            http_proto(clntSocket, curToSend); 
	}
        
         VPRINTF(("Socket %d says goodbye.\n", (int)clntSocket));
         fflush(stdout); 
/*
        #ifdef _WIN32
            closesocket(clntSocket);
        #else
            close(clntSocket); 
        #endif
*/
    } //end main loop

    return NULL; //never reached
}

